#include "tbcore/reflection/lua_types.hpp"

extern "C" {
#include <luajit/luajit.h>
#include <luajit/lauxlib.h>
}

#include "tbcore/base/unique_id.hpp"

#include "tbcore/reflection/reflection.hpp"
#include "tbcore/reflection/variant.hpp"
#include "tbcore/reflection/property.hpp"
#include "tbcore/reflection/function.hpp"
#include "tbcore/reflection/object.hpp"

TB_NAMESPACE_BEGIN

static const char* kTableTypeNames[]	= 
{ "ysClass",  "ysVariant",  "ysMemFunction", "ysPureFunction"};
static const char* kMetaTableTypeNames[] = 
{ "ys.Class", "ys.Variant", "ys.MemFunction", "ys.PureFunction"};

static const char* kLuaTypeField = "__TBTYPE__";
static const char* kReturnSymbal = "__RETURN__";

namespace lua {

int LuaVariantDeleter(lua_State* L) {
	Variant* v = (Variant*)lua_touserdata(L, -1);
	v->~Variant();
	return 0;
}

int LuaNoneDeleter(lua_State* L) {
	(void)L;
	return 0;
}

int LuaMemFunctionCaller(lua_State* L) {
	if (MemFunctionType* v = (MemFunctionType*)lua_touserdata(L, 1)) {
		uint32 argc = (uint32)(lua_gettop(L) - 1);
		if (v->func->GetArgCount() == argc) {
			std::stack<Variant> params;
			for (uint32 i = 0; i < argc; ++i) {
				params.push(GetStackValue(L, i + 2));
			}
			
      v->func->BindObject(v->data->DataPtr());
      if (v->func->HasRetValue()) {
        Variant ret = v->func->InvokeWithRet(params);
        if (!ret.IsNull()) {
          PushStackValue(L, ret);
          return 1;
        }
      } else {
        v->func->InvokeWithoutRet(params);
      }
		}
	}
	return 0;
}

int LuaPureFunctionCaller(lua_State* L) {
	if (PureFunctionType* v = (PureFunctionType*)lua_touserdata(L, 1)) {
    int32 argc = (int32)(lua_gettop(L) - 1);
		if (v->func->GetArgCount() == argc) {
			std::stack<Variant> params;
      for (int32 i = argc - 1; i >= 0; --i) {
				params.push(GetStackValue(L, i + 2));
			}
			Variant ret;
      if (v->func->HasRetValue()) {
        Variant ret = v->func->InvokeWithRet(params);
        if (!ret.IsNull()) {
          PushStackValue(L, ret);
          return 1;
        }
      } else {
        v->func->InvokeWithoutRet(params);
      }
		}
	}
	return 0;
}

int ImplementEqProc(lua_State* l) {
  return 0;
}

Variant GetStackValue(void* l, int idx) {
  lua_State* L = (lua_State*)l;
	if (lua_gettop(L) > 0) {
		switch (lua_type(L, idx)) {
			case LUA_TSTRING: {
				std::size_t len;
				const char* str = lua_tolstring(L, idx, &len);
				return Variant(str, (uint32)len);
			}
			case LUA_TNUMBER: {
				return Variant(lua_tonumber(L, idx));
			}
			case LUA_TBOOLEAN: {
				return Variant(lua_toboolean(L, idx) != 0);
			}
			case LUA_TUSERDATA: {
				TableType t = GetUserDataType(L, idx);
				switch (t)
				{
					case kClassTable: 
					case kVariantTable: {
						return *(Variant*)lua_touserdata(L, -1);
					}
				}
			}
			case LUA_TTABLE: 
			case LUA_TFUNCTION: 
			case LUA_TTHREAD: 
			case LUA_TLIGHTUSERDATA:
			case LUA_TNIL:
			default:{
				//ignored
				break;
			} 
		}
	}
	return Variant();
}

void NewClassMetatable(void* l, const TCHAR* classname, LuaFunctionT newproc,
  LuaFunctionT getproc, LuaFunctionT setproc) {
  lua_State* L = (lua_State*)l;

  _STD string classname_ = TB_ASCII_STRING(classname);

	//create new or return exist
	_STD string tname = _STD string("TB.") + classname_;
	luaL_newmetatable(L, tname.c_str());

		//set equal delegate
		lua_pushstring(L, kLuaTypeField);
		lua_setfield(L, -2, kTableTypeNames[kClassTable]);

		//set equal delegate
		lua_pushcfunction(L, (lua_CFunction)newproc);
		lua_setfield(L, -2, "new");

		//set equal delegate
    lua_pushcfunction(L, (lua_CFunction)ImplementEqProc);
		lua_setfield(L, -2, "__eq");

		//set index/get delegate
    lua_pushcfunction(L, (lua_CFunction)getproc);
		lua_setfield(L, -2, "__index");

		//set newindex/set delegate
    lua_pushcfunction(L, (lua_CFunction)setproc);
		lua_setfield(L, -2, "__newindex");

		//set gc proc
		lua_pushcfunction(L, LuaVariantDeleter);
		lua_setfield(L, -2, "__gc");

	//pop metatable whatever
	lua_setglobal(L, classname_.c_str());
}

void PushClassMetatable(lua_State* L, const TCHAR* classname) {
	_STD string str = _STD string("TB.") + TB_ASCII_STRING(classname);
	luaL_newmetatable(L, str.c_str());
}

void NewVariantMetatable(void* l) {
  lua_State* L = (lua_State*)l;

	//create new or return exist
	luaL_newmetatable(L, kMetaTableTypeNames[kVariantTable]);

		//set equal delegate
		lua_pushstring(L, kLuaTypeField);
		lua_setfield(L, -2, kTableTypeNames[kVariantTable]);

		//set gc proc
		lua_pushcfunction(L, LuaVariantDeleter);
		lua_setfield(L, -2, "__gc");

	//pop metatable whatever
	lua_pop(L, 1);
}

void lua::NewMemFunctionMetatable(void* l) {
  lua_State* L = (lua_State*)l;

	//create new or return exist
	luaL_newmetatable(L, kMetaTableTypeNames[kMemFunctionTable]);

		//set equal delegate
		lua_pushstring(L, kLuaTypeField);
		lua_setfield(L, -2, kTableTypeNames[kMemFunctionTable]);

		//set gc proc
		lua_pushcfunction(L, LuaNoneDeleter);
		lua_setfield(L, -2, "__gc");

		//set gc proc
		lua_pushcfunction(L, LuaMemFunctionCaller);
		lua_setfield(L, -2, "__call");

	//pop metatable whatever
	lua_pop(L, 1);
}

void lua::NewPureFunctionMetatable(void* l) {
  lua_State* L = (lua_State*)l;

	//create new or return exist
	luaL_newmetatable(L, kMetaTableTypeNames[kPureFunctionTable]);

	  //set equal delegate
	  lua_pushstring(L, kLuaTypeField);
	  lua_setfield(L, -2, kTableTypeNames[kPureFunctionTable]);

	  //set gc proc
	  lua_pushcfunction(L, LuaNoneDeleter);
	  lua_setfield(L, -2, "__gc");

	  //set gc proc
	  lua_pushcfunction(L, LuaPureFunctionCaller);
	  lua_setfield(L, -2, "__call");

	//pop metatable whatever
	lua_pop(L, 1);
}

TableType GetUserDataType(void* l, int idx) {
  lua_State* L = (lua_State*)l;

	luaL_getmetafield(L, idx, kLuaTypeField);
	if (lua_isstring(L, -1)) {
		const char* typeStr = lua_tostring(L, -1);
		uint32 arrsize = TB_ARRAYSIZE(kTableTypeNames);
		for (uint32 i = 0; i < arrsize; ++i) {
			if (strcmp(typeStr, kTableTypeNames[i]) == 0) {
				return (TableType)i;
			}
		}
	}
	return kUnknownTable;
}

int LuaReturnProc(lua_State* L) {
  lua_setglobal(L, kReturnSymbal);
  return 0;
}

void InitializeLuaState(void* l) {
  lua_State* L = (lua_State*)l;

	NewVariantMetatable(L);
	NewMemFunctionMetatable(L);
  NewPureFunctionMetatable(L);

  //register return function
  lua_pushcfunction(L, (lua_CFunction)LuaReturnProc);
  lua_setglobal(L, "return");

  //register global functions
  const reflection::ConstFunctionsType fns = reflection::GetGlobalFunctions();
  for (reflection::ConstFunctionsType::const_iterator it = fns.begin();
    it != fns.end();
    ++it) {
    PureFunctionType* retData = (PureFunctionType*)lua_newuserdata(L, sizeof(PureFunctionType));
    retData->func = (*it);
    PushPureFunctionMetatable(L);
    lua_setmetatable(L, -2);
    _STD string fname = TB_ASCII_STRING(retData->func->name);
    lua_setglobal(L, fname.c_str());
  }

  const reflection::BindingProcsType& pocs = reflection::GetBindingProcs();
  for (reflection::BindingProcsType::const_iterator it = pocs.begin();
    it != pocs.end();
    ++it) {
    (*it)(l);
  }
}

void PushVariantMetatable(void* l) {
  lua_State* L = (lua_State*)l;
	luaL_newmetatable(L, kMetaTableTypeNames[kVariantTable]);
}

void PushMemFunctionMetatable(void* l) {
  lua_State* L = (lua_State*)l;
	luaL_newmetatable(L, kMetaTableTypeNames[kMemFunctionTable]);
}

void PushPureFunctionMetatable(void* l) {
  lua_State* L = (lua_State*)l;
  luaL_newmetatable(L, kMetaTableTypeNames[kPureFunctionTable]);
}

void PushStackValue(void* l, const Variant& val) {
  lua_State* L = (lua_State*)l;
	switch (val.TypeId()) {
		case Variant::kNullType: {
			lua_pushnil(L);
			break;
		}
		case Variant::kBoolType: {
			lua_pushboolean(L, val.Value<bool>());
			break;
		}
		case Variant::kInt8Type: 
		case Variant::kUint8Type:
		case Variant::kInt16Type:
		case Variant::kUint16Type:
		case Variant::kInt32Type:
		case Variant::kUint32Type:
		case Variant::kInt64Type: 
		case Variant::kUint64Type:
		case Variant::kFloatType:
		case Variant::kDoubleType:{
			lua_pushnumber(L, val.Value<lua_Number>());
			break;
		}
		case Variant::kStringType: {
			_STD string str = val.Value<_STD string>();
			lua_pushlstring(L, str.data(), str.size());
			break;
		}
		default: {
			Variant* retData = (Variant*)lua_newuserdata(L, sizeof(Variant));
			new (retData)Variant(val);
			lua::PushVariantMetatable(L);
			lua_setmetatable(L, -2);
			break;
		}
	}
}

int ImplementNewProc(void* l, const TCHAR* className, const Variant& instance) {
  lua_State* L = (lua_State*)l;

  TB_NAMESPACE::Variant* udata = (TB_NAMESPACE::Variant*)lua_newuserdata(L, sizeof(TB_NAMESPACE::Variant));
  new (udata) Variant(instance);
  TB_NAMESPACE::lua::PushClassMetatable(L, className);
  lua_setmetatable(L, -2);
  return 1;
}

TB_REFLECTION_API int ImplementGetProc(void* l, GetClassFunctionT funGetter, GetClassPropertyT propGetter) {
  lua_State* L = (lua_State*)l;

  Variant* udata = (Variant*)lua_touserdata(L, -2);
  if (!udata) {
    luaL_argerror(L, 1, "invalid class data!");
    return 0;
  }
  std::size_t len;
  const char* index = luaL_checklstring(L, -1, &len);
  if (!index || len == 0) {
    luaL_argerror(L, 2, "index is not string");
    return 0;
  }

  String index_ = TB_STRING(std::string(index));
  if (const reflection::Function* func = funGetter(index_.c_str())) {
    TB_NAMESPACE::lua::MemFunctionType* fdata = (TB_NAMESPACE::lua::MemFunctionType*)lua_newuserdata(L, sizeof(lua::MemFunctionType));
    fdata->data = udata;
    fdata->func = func;
    lua::PushMemFunctionMetatable(L);
    lua_setmetatable(L, -2);
    return 1;
  } else if (const reflection::Property* prop
    = propGetter(index_.c_str())) {
    PushStackValue(l, prop->Get(udata->DataPtr()));
    return 1;
  } else {
    luaL_argerror(L, 2, "unknown index!");
    return 0;
  }
  return 0;
}

TB_REFLECTION_API int ImplementSetProc(void* l, GetClassFunctionT funGetter, GetClassPropertyT propGetter) {
  lua_State* L = (lua_State*)l;

  (void)funGetter;

  TB_NAMESPACE::Variant* udata = (TB_NAMESPACE::Variant*)lua_touserdata(L, -3);
  if (!udata) {
    luaL_argerror(L, 1, "invalid class data!");
    return 0;
  }
  std::size_t len;
  const char* index = luaL_checklstring(L, -2, &len);
  if (!index || len == 0) {
    luaL_argerror(L, 2, "index is not string");
    return 0;
  }
  TB_NAMESPACE::Variant val = lua::GetStackValue(L, -1);
  String index_ = TB_STRING(std::string(index));
  if (const TB_NAMESPACE::reflection::Property* prop = propGetter(index_.c_str())) {
    prop->Set(udata->DataPtr(), val);
  }
  return 0;
}

void* NewLuaState() {
  return luaL_newstate();
}

int GetStackSize(void* l) {
  return lua_gettop((lua_State*)l);
}

Variant GetReturnValue(void* l) {
  lua_State* L = (lua_State*)l;
  lua_getglobal(L, kReturnSymbal);
  return GetStackValue(l, -1);
}

Result EvalString(void* l, const String& str) {
  std::string str_ = TB_ASCII_STRING(str);
  int ret = luaL_dostring((lua_State*)l, str_.c_str());
  if (ret) {
    const char* err = lua_tostring((lua_State*)l, -1);
    return Result(kErrorEvalLuaScript, TB_STRING(std::string(err)));
  }
  return Result();
}

void DeleteLuaState(void *l) {
  lua_close((lua_State*)l);
}

} // namespace lua

TB_NAMESPACE_END
